#include <buola/gui/x11/auto.h>

#include <buola/gui/cwindow.h>
#include <buola/gui/x11/cx11window.h>
#include <buola/gui/x11/cx11clipboard.h>
#include <buola/gui/x11/cx11toolkit.h>
#include <buola/gui/x11/cx11display.h>

#include <X11/Xatom.h>

#define MOMENT_NONE 0
#define MOMENT_ASKTARGETS   1
#define MOMENT_ASKDATA      2

namespace buola { namespace gui { namespace x11 {

CX11Clipboard::CX11Clipboard(int pType)
    : CClipboard(pType)
{
    mAtom=TypeToAtom(pType);
    mProxy=nullptr;
    mClientMoment=MOMENT_NONE;
}

//////////////////////
// server interface
//

bool CX11Clipboard::OwnSelection()
{
    CX11Window *lProxy=throwing_cast<CX11Window*>(GetProxy());
    mOwned=lProxy->AcquireSelection(mAtom);
    return mOwned;
}

bool CX11Clipboard::ReleaseSelection()
{
    if(!mOwned) return false;
    CX11Window *lProxy=throwing_cast<CX11Window*>(GetProxy());
    lProxy->ReleaseSelection(mAtom);
    mOwned=false;
    return true;
}

//////////////////////
// client interface
//

void CX11Clipboard::InitTargets()
{
    mAvailableTargets.clear();
    AddTarget(get_default_x11_display()->Atoms().mTargets);
    AddTarget(get_default_x11_display()->Atoms().mMultiple);
    AddTarget(get_default_x11_display()->Atoms().mUTF8);
    AddTarget(XA_STRING);
    AddTarget(get_default_x11_display()->Atoms().mCompound);
    AddTarget(get_default_x11_display()->Atoms().mTextPlain);
}

void CX11Clipboard::AddTarget(Atom pAtom)
{
    mAvailableTargets.push_back(pAtom);
}

void CX11Clipboard::AddTarget(const std::wstring &pString)
{
    mAvailableTargets.push_back(get_default_x11_display()->GetAtom(pString));
}

void CX11Clipboard::AskForSelection(int pTarget)
{
    mClientMoment=MOMENT_ASKTARGETS;
    mRequestedTarget=pTarget;

    AskForData(get_default_x11_display()->Atoms().mTargets);
}

/////////////////////
// server internal
//

void CX11Clipboard::ServeTargetsList()
{
    get_default_x11_display()->SetWindowProperty(mPartner,mProperty,mAvailableTargets);
}

void CX11Clipboard::ServeText(const std::wstring &pString)
{
    get_default_x11_display()->SetWindowProperty(mPartner,mProperty,mTarget,pString);
}

void CX11Clipboard::ServeData(void*,std::size_t)
{
}

/////////////////////
// client internal
//

bool CX11Clipboard::IsTargetAvailable(Atom pAtom)
{
    for(int i=0;i<mAvailableTargets.size();i++)
    {
        if(mAvailableTargets[i]==pAtom) return true;
    }
    
    return false;
}

bool CX11Clipboard::IsTargetAvailable(const std::wstring& pAtom)
{
    return IsTargetAvailable(get_default_x11_display()->GetAtom(pAtom));
}

void CX11Clipboard::AskForData(Atom /*pTarget*/)
{
    ///\todo
//    XConvertSelection(get_default_x11_display()->GetDisplay(),mAtom,pTarget,
//                      get_default_x11_display()->Atoms().mBuolaTransfer,GetProxy()->GetID(),CurrentTime);
}

///////////////
// callbacks
//

void CX11Clipboard::ProcessSelectionRequest(XSelectionRequestEvent *pE)
{
    bool lSuccesful=false;

    mPartner=pE->requestor;
    mProperty=pE->property?:get_default_x11_display()->Atoms().mBuolaTransfer;
    mTarget=pE->target;
    mMIMETarget=get_default_x11_display()->GetAtomName(mTarget);

    //Here we have the target, which isn't the same as the property type,
    //but the type can be deduced from the target. Some targets have an
    //associated type, and some targets have special behaviours

if(mTarget==get_default_x11_display()->Atoms().mTargets)
    {
        ServeTargetsList();
        lSuccesful=true;
    }
    else if(mTarget==get_default_x11_display()->Atoms().mMultiple)
    {
        msg_warn() << "Requested for multiple targets in CX11Clipboard\n";
        lSuccesful=false;
    }
    else if(mTarget==get_default_x11_display()->Atoms().mUTF8||
        mTarget==get_default_x11_display()->Atoms().mCompound||
        mTarget==get_default_x11_display()->Atoms().mTextPlain||
            mTarget==XA_STRING)
    {
        ServeText(mTextContents);
        lSuccesful=true;
    }
    else
    {
        lSuccesful=ProvideData(mMIMETarget);
    }

    XEvent lXE;
    
    lXE.xselection.type=SelectionNotify;
    lXE.xselection.requestor=mPartner;
    lXE.xselection.selection=pE->selection;
    lXE.xselection.target=pE->target;
    lXE.xselection.property=lSuccesful?mProperty:0;
    lXE.xselection.time=pE->time;
    
    XSendEvent(get_default_x11_display()->GetDisplay(),pE->requestor,false,0,&lXE);
}

void CX11Clipboard::ProcessSelectionClear(XSelectionClearEvent*)
{
    if(!mOwned) return;
    
    mOwned=false;
    SelectionLost();
}

void CX11Clipboard::ProcessSelectionNotify(XSelectionEvent *pE)
{
    if(mClientMoment==MOMENT_ASKTARGETS)
    {
        if(!pE->property)
        {
            msg_warn() << "Conversion failed\n";
            SelectionFailed();
            mClientMoment=MOMENT_NONE;
            return;
        }

        if(pE->target!=get_default_x11_display()->Atoms().mTargets)
        {
            msg_warn() << "Expected targets but received something else\n";
        }
  
        ///\todo      
//        get_default_x11_display()->GetWindowProperty(GetProxy()->GetID(),
//                                        get_default_x11_display()->Atoms().mBuolaTransfer,mAvailableTargets);
            
        if(mRequestedTarget==TYPE_TARGETS)
        {
            SelectionReady(L"");
            mClientMoment=MOMENT_NONE;
        }
        else if(mRequestedTarget==TYPE_STRING)
        {
            if(IsTargetAvailable(get_default_x11_display()->Atoms().mUTF8))
            {
                AskForData(get_default_x11_display()->Atoms().mUTF8);
                mActualType=get_default_x11_display()->Atoms().mUTF8;
                mClientMoment=MOMENT_ASKDATA;
            }
            else if(IsTargetAvailable(XA_STRING))
            {
                AskForData(XA_STRING);
                mActualType=XA_STRING;
                mClientMoment=MOMENT_ASKDATA;
            }
            else if(IsTargetAvailable(get_default_x11_display()->Atoms().mTextPlain))
            {
                AskForData(get_default_x11_display()->Atoms().mTextPlain);
                mActualType=get_default_x11_display()->Atoms().mTextPlain;
                mClientMoment=MOMENT_ASKDATA;
            }
            else
            {
                SelectionFailed();
                mClientMoment=MOMENT_NONE;
            }
        }
        else if(mRequestedTarget==TYPE_URI)
        {
            if(IsTargetAvailable(get_default_x11_display()->Atoms().mTextUrilist))
            {
                AskForData(get_default_x11_display()->Atoms().mTextUrilist);
                mActualType=get_default_x11_display()->Atoms().mTextUrilist;
                mClientMoment=MOMENT_ASKDATA;
            }
            else
            {
                SelectionFailed();
                mClientMoment=MOMENT_NONE;
            }
        }
    }
    else if(mClientMoment==MOMENT_ASKDATA)
    {
        msg_warn() << "Received data\n";
        if(mRequestedTarget==TYPE_STRING||mRequestedTarget==TYPE_URI)
        {
            std::wstring lReturn;
            ///\todo
//            get_default_x11_display()->GetWindowProperty(GetProxy()->GetID(),
//                                    get_default_x11_display()->Atoms().mBuolaTransfer,mActualType,lReturn);
            SelectionReady(lReturn);
        }
    }
    else
    {
        msg_warn() << "Received selection notify in wrong moment\n";
    }

}

CPhysicalWindow *CX11Clipboard::GetProxy()
{
    if(!mProxy) mProxy=get_x11_toolkit()->GetNewProxyWindow();
    return mProxy;
}

int CX11Clipboard::AtomToType(Atom pAtom)
{
    if(pAtom==get_default_x11_display()->Atoms().mClipboard)
        return CLIPBOARD;
    else if(pAtom==get_default_x11_display()->Atoms().mPrimary)
        return PRIMARY;
    else if(pAtom==get_default_x11_display()->Atoms().mSecondary)
        return SECONDARY;
    else if(pAtom==get_default_x11_display()->Atoms().mDndSelection)
        return DRAGNDROP;
    else
        throw XNotFound(I18N("Requested non-existent clipboard"));  
}

Atom CX11Clipboard::TypeToAtom(int pType)
{
    switch(pType)
    {
    case CLIPBOARD:
        return get_default_x11_display()->Atoms().mClipboard;
        break;
    case PRIMARY:
        return get_default_x11_display()->Atoms().mPrimary;
        break;
    case SECONDARY:
        return get_default_x11_display()->Atoms().mSecondary;
        break;
    case DRAGNDROP:
        return get_default_x11_display()->Atoms().mDndSelection;
        break;
    default:
        throw XNotFound(I18N("Creating non-existent clipboard"));   
    }
}


/*namespace x11*/ } /*namespace gui*/ } /*namespace buola*/ }
